package org.terramagnet.learn.rabbitmq.consumer.framework;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

/**
 * 系统关闭时自动保存数据，启动时自动加载数据. 需要保存的对象必须实现{@link Serializable 序列化}接口.
 *
 * @author lip
 */
@Component
public abstract class FilePersistent<T extends Serializable> {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    @Value("#{config['persistence.file'] ?: '${user.home}/.system-persistence'}")
    private String store;
    @Value("#{config['persistence.noninvasive'] ?: false}")
    private boolean noninvasive;

    @Autowired
    private ApplicationContext applicationContext;
    private boolean loaded = false;

    /**
     * 系统启动时调用. 在读取数据之前调用。
     *
     * 默认空实现。
     */
    protected void startup() {
    }

    /**
     * 系统启动时从文件中读到了数据. <strong>没有读取到数据时不会调用该方法！</strong>
     *
     * @param data 读到的数据
     * @throws Exception 方法抛出的任何异常都会被忽略
     */
    protected abstract void fromLoad(T data) throws Exception;

    /**
     * 系统关闭时准备保存数据到文件. 返回{@code null}或{@link Collection#isEmpty() empty}的集合表示没有需要保存的数据。
     * <p>
     * <strong>需要保存的对象必须实现{@link Serializable 序列化}接口</strong></p>
     *
     * @return 需要保存的数据或{@code null}
     * @throws Exception 方法抛出的任何异常都会被忽略
     */
    protected abstract T forSave() throws Exception;

    /**
     * 数据文件的名称.
     */
    protected String getStoreName() {
        return getClass().getName();
    }

    /**
     * 从文件中读取对象.
     *
     * <p>
     * 系统启动时自动执行的代码. <strong>不要在代码中手动调用！</strong></p>
     */
    @PostConstruct
    public void load() {
        startup();
        
        StringBuilder persist = replaceVariablesToFilePath(store);
        persist.append(File.separatorChar).append(getStoreName());

        File file = new File(persist.toString());
        logger.debug("persistence={}", file);
        if (!file.exists()) {
            loaded = true;
            return;
        }
        ObjectInputStream is = null;
        T data = null;
        try {
            is = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
            data = (T) is.readObject();
        } catch (IOException ex) {
            logger.error("从文件中读取数据出错：" + file, ex);
        } catch (ClassNotFoundException ex) {
            logger.error("从文件中读取数据出错：对象序列化类型不匹配", ex);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException ex) {
                    logger.warn("文件流关闭错误", ex);
                }
            }
        }
        loaded = true;
        if ((data instanceof Collection) && ((Collection) data).isEmpty()) {
            if (noninvasive) {
                file.delete();
            }
            return;
        }
        try {
            fromLoad(data);
        } catch (Exception ex) {
            logger.warn("加载数据出错", ex);
        }
        if (noninvasive) {
            file.delete();
        }
    }

    /**
     * 保存对象到文件.
     * <p>
     * 系统关闭时自动执行的代码. <strong>不要在代码中手动调用！</strong></p>
     */
    @PreDestroy
    public void save() {
        Object data = null;
        try {
            data = forSave();
        } catch (Exception ex) {
            logger.warn("保存数据出错", ex);
        }
        if (data == null
            || (data instanceof Collection) && ((Collection) data).isEmpty()) {
            return;
        }

        String persist = replaceVariablesToFilePath(store).toString();
        File directory = new File(persist);
        if (!directory.exists()) {
            directory.mkdirs();
        }
        ObjectOutputStream os = null;
        File file = new File(directory, getStoreName());
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            os = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
            logger.debug("save to persistence --> {}", file);
            os.writeObject(data);
        } catch (IOException ex) {
            logger.error("保存数据到文件出错：" + file, ex);
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException ex) {
                    logger.warn("文件流关闭错误", ex);
                }
            }
        }
    }

    /**
     * 设置文件保存目录.
     */
    public void setStore(String store) {
        this.store = store;
    }

    /**
     * 设置非侵入性. <i>非侵入的</i>表示成功加载完数据后会删除上次保存的文件。
     *
     * @param noninvasive true--非侵入的
     */
    public void setNoninvasive(boolean noninvasive) {
        this.noninvasive = noninvasive;
    }

    private static final Pattern REGEXP_TEMPLATE_VARIABLES = Pattern.compile("\\$\\{((?!\\d)[0-9a-zA-Z.]+)\\}", Pattern.CASE_INSENSITIVE);

    private StringBuilder replaceVariablesToFilePath(String template) {
        Matcher matcher = REGEXP_TEMPLATE_VARIABLES.matcher(template);
        Environment environment = applicationContext.getEnvironment();

        int prev = 0;
        StringBuilder text = new StringBuilder();
        while (matcher.find()) {
            String variableName = matcher.group(1);
            String variableValue = environment.getProperty(variableName);
            if (variableValue != null) {
                text.append(template.substring(prev, matcher.start()).replace('/', File.separatorChar))
                    .append(variableValue);
                prev = matcher.end();
            }
        }
        if (prev < template.length()) {
            text.append(template.substring(prev));
        }
        return text;
    }

    /**
     * 是否已加载过数据. 而不管有没有加载到数据！
     */
    protected boolean isLoaded() {
        return loaded;
    }
}
